Skip to content

feat: collect LLDP data on DPU and host (1/3)#3418

Open
kurotych wants to merge 12 commits into
NVIDIA:mainfrom
kurotych:lldp-info-cli
Open

feat: collect LLDP data on DPU and host (1/3)#3418
kurotych wants to merge 12 commits into
NVIDIA:mainfrom
kurotych:lldp-info-cli

Conversation

@kurotych

Copy link
Copy Markdown
Contributor

This is the first of three parts implementing the LLDP collection feature described in #3204.

  • Implement the lldp_collector.rs module, which collects the machine's LLDP neighbor data.
  • Add an lldp-neighbors CLI command to the scout and agent services.
  • Add lldpd packet to scout os

Details

Ported and fixed abandoned code

dpu.rs contained leftovers from an earlier attempt at LLDP data collection. That code has been fixed and ported into lldp_collector.rs.

Unlike the old code, which lived in the DPU enumeration path, lldp_collector.rs is machine-agnostic: it enumerates the Ethernet interfaces on any host (DPU, scout, etc.) and collects their LLDP neighbors uniformly. It also filters out self-loopback neighbors.

Concerns multiple neighbors on a single port: the old code assumed this was possible but handled it in a strange manner: only the last neighbor survived (get_port_lldp_info overwrote the result on each iteration).

The other change is switching lldpcli to the -f json0 output format, which is more stable to parse programmatically. The map-keyed -f json format uses dynamic keys (port names, neighbor names) as JSON object keys, whereas json0 renders everything as arrays of objects with fixed keys

Manual test results

The real values were changed

ubuntu@scout:~$ sudo ./forge-scout lldp-neighbors
58:a2:e1:00:00:b0: LldpSwitchData {
    name: "scout-host-01.example.local",
    id: "mac=58:a2:e1:00:00:d4",
    description: "Example-SRE (sre@example.com), <redacted-node-token>",
    local_port: "enp1s0np0",
    ip_address: [
        "192.0.2.195",
        "2001:db8::1",
    ],
    remote_port: "mac=12:00:00:00:4c:eb",
    id_type: "mac",
    id_value: "58:a2:e1:00:00:d4",
    remote_port_type: "mac",
    remote_port_value: "12:00:00:00:4c:eb",
}
6c:b3:11:00:00:70: LldpSwitchData {
    name: "example-switch-01",
    id: "mac=00:01:e8:00:00:ac",
    description: "Dell Real Time Operating System Software. Dell Operating System Version: 2.0. Dell Application Software Version: 9.14(1.10) Copyright (c) 1999-2018 Dell Inc. All Rights Reserved.Build Time: Thu Sep 23 15:32:37 2021",
    local_port: "ens11f0",
    ip_address: [
        "198.51.100.76",
    ],
    remote_port: "ifname=TenGigabitEthernet 0/14",
    id_type: "mac",
    id_value: "00:01:e8:00:00:ac",
    remote_port_type: "ifname",
    remote_port_value: "TenGigabitEthernet 0/14",
}
6c:b3:11:00:00:71: LldpSwitchData {
    name: "example-switch-01",
    id: "mac=00:01:e8:00:00:ac",
    description: "Dell Real Time Operating System Software. Dell Operating System Version: 2.0. Dell Application Software Version: 9.14(1.10) Copyright (c) 1999-2018 Dell Inc. All Rights Reserved.Build Time: Thu Sep 23 15:32:37 2021",
    local_port: "ens11f1",
    ip_address: [
        "198.51.100.76",
    ],
    remote_port: "ifname=TenGigabitEthernet 0/1",
    id_type: "mac",
    id_value: "00:01:e8:00:00:ac",
    remote_port_type: "ifname",
    remote_port_value: "TenGigabitEthernet 0/1",
}
ubuntu@dpu-mgmt:~$ sudo ./forge-dpu-agent lldp-neighbors
58:a2:e1:00:00:c0: LldpSwitchData {
    name: "example-switch-04",
    id: "mac=24:8a:07:00:00:aa",
    description: "Cumulus Linux version 5.11.1 running on Mellanox Technologies Ltd. Mellanox switch",
    local_port: "p0",
    ip_address: [
        "192.0.2.101",
        "fe80::268a:7ff:fe00:aa",
    ],
    remote_port: "ifname=swp1",
    id_type: "mac",
    id_value: "24:8a:07:00:00:aa",
    remote_port_type: "ifname",
    remote_port_value: "swp1",
}
12:00:00:00:4c:eb: LldpSwitchData {
    name: "scout",
    id: "mac=6c:b3:11:00:00:70",
    description: "Ubuntu 24.04.4 LTS Linux 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64",
    local_port: "pf0hpf",
    ip_address: [
        "192.0.2.5",
        "fe80::6eb3:11ff:fe00:70",
    ],
    remote_port: "mac=58:a2:e1:00:00:b0",
    id_type: "mac",
    id_value: "6c:b3:11:00:00:70",
    remote_port_type: "mac",
    remote_port_value: "58:a2:e1:00:00:b0",
}
58:a2:e1:00:00:c1: LldpSwitchData {
    name: "example-switch-05",
    id: "mac=b8:59:9f:00:00:72",
    description: "Cumulus Linux version 5.11.1 running on Mellanox Technologies Ltd. MSN2100",
    local_port: "p1",
    ip_address: [
        "192.0.2.102",
        "fe80::ba59:9fff:fe00:72",
    ],
    remote_port: "ifname=swp1",
    id_type: "mac",
    id_value: "b8:59:9f:00:00:72",
    remote_port_type: "ifname",
    remote_port_value: "swp1",
}

To be done in later PRs

  • Part 2. Poll LLDP info on the agent/scout side and periodically report it (only when it changed since the last report) to nico-api via a new gRPC endpoint (and separate table).
  • Part 3. Expose the information through the REST API.

Related issues

#3204

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Additional Notes

kurotych added 7 commits July 8, 2026 20:11
Move LLDP parsing (get_port_lldp_info and friends) out of the
DPU-specific enumeration into a shared lldp_collector module.

Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
… port

Rework lldp collector to consume `lldpcli -f json0` instead of `-f json`.
json0 emits a stable, uniform structure — every field is an array with fixed
keys — whereas `-f json` makes the output structure data-dependent and fragile
to parse.

Add split id_type/id_value and remote_port_type/remote_port_value fields to
LldpSwitchData; the combined id/remote_port strings are kept and marked
deprecated for backward compatibility

Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
…oting

Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
@kurotych kurotych requested a review from a team as a code owner July 13, 2026 10:50
@copy-pr-bot

copy-pr-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change introduces a dedicated LLDP collector, updates RPC fields, removes embedded DPU LLDP logic, adds agent and scout CLI commands, and installs and enables lldpd in scout images.

Changes

LLDP discovery

Layer / File(s) Summary
LLDP RPC contract
crates/rpc/proto/machine_discovery.proto, rest-api/.../machine_discovery.proto, crates/rpc/src/model/hardware_info.rs
Adds split LLDP identity and inventory fields, deprecates combined fields, and supplies defaults during conversions.
Dedicated LLDP collector
crates/host-support/src/lldp_collector.rs, crates/host-support/src/lib.rs
Enumerates Ethernet interfaces, reads MAC addresses, queries and parses lldpcli JSON, filters local chassis loops, prints neighbors, and tests parsing behavior.
DPU LLDP decoupling
crates/host-support/src/hardware_enumeration.rs, crates/host-support/src/hardware_enumeration/dpu.rs
Removes embedded LLDP models and queries, leaves DPU switch data empty, and widens helper visibility.
Local LLDP commands
crates/agent/src/*, crates/scout/src/*
Adds LldpNeighbors subcommands that print neighbors and exit before normal startup.
lldpd image setup
pxe/mkosi.profiles/scout-oss-*/mkosi.conf, pxe/mkosi.profiles/scout-oss-*/mkosi.postinst.chroot
Installs lldpd and explicitly enables its service in both scout image profiles.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant ScoutOrAgent
  participant LldpCollector
  participant UdevAndSysfs
  participant Lldpcli
  Operator->>ScoutOrAgent: invoke LldpNeighbors
  ScoutOrAgent->>LldpCollector: print_lldp_neighbors()
  LldpCollector->>UdevAndSysfs: enumerate interfaces and read MACs
  UdevAndSysfs-->>LldpCollector: return interfaces and MACs
  LldpCollector->>Lldpcli: query neighbors and local chassis as JSON
  Lldpcli-->>LldpCollector: return LLDP responses
  LldpCollector-->>ScoutOrAgent: print neighbor output
  ScoutOrAgent-->>Operator: exit before normal startup
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: collecting LLDP data on DPU and host, and notes this is part 1 of 3.
Description check ✅ Passed The description is directly aligned with the changeset and accurately describes the LLDP collector, CLI additions, and Scout OS package update.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/host-support/src/hardware_enumeration.rs (1)

157-157: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow visibility to pub(crate) for consistency and minimal surface.

The only new consumer (lldp_collector) references this through crate::hardware_enumeration::convert_property_to_string, so pub(crate) is sufficient. Since the helper was previously private, no external crate depends on it. This also matches the pub(crate) you correctly chose for PCI_SUBCLASS on Line 45.

♻️ Proposed change
-pub fn convert_property_to_string<'a>(
+pub(crate) fn convert_property_to_string<'a>(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host-support/src/hardware_enumeration.rs` at line 157, Change the
visibility of convert_property_to_string from pub to pub(crate), keeping its
existing signature and behavior unchanged. Ensure the lldp_collector crate-local
reference continues to resolve through crate::hardware_enumeration.
crates/host-support/src/lldp_collector.rs (1)

177-177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Emit errors as structured fields, not string interpolation. These log sites interpolate the error (and raw JSON payloads) into the message string, which defeats logfmt key/value search. As per coding guidelines, prefer error = %e and named fields over interpolation (tracing::error!(%machine_id, error=%e, "...")).

  • crates/host-support/src/lldp_collector.rs#L177-L177: replace warn!("Could not deserialize local LLDP chassis {json}, {e}") with warn!(error = %e, "Could not deserialize local LLDP chassis") (drop or demote the full json payload to a debug field).
  • crates/host-support/src/lldp_collector.rs#L158-L158: replace with warn!(error = %e, "Could not read local LLDP chassis").
  • crates/host-support/src/lldp_collector.rs#L224-L224: replace tracing::debug!(ifname, "no LLDP neighbor: {e}") with tracing::debug!(ifname, error = %e, "no LLDP neighbor").
  • crates/host-support/src/lldp_collector.rs#L237-L237: replace with warn!(port, error = %e, "Could not discover LLDP peer").
  • crates/host-support/src/lldp_collector.rs#L255-L255: replace with warn!(error = %e, "Could not deserialize LLDP response") (payload to a debug field).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host-support/src/lldp_collector.rs` at line 177, Update all five LLDP
logging sites in lldp_collector.rs (lines 158, 177, 224, 237, and 255) to emit
errors as structured tracing fields using error = %e and concise static
messages; preserve ifname and port fields where present, and drop or demote raw
JSON payloads to debug fields at the deserialization sites.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host-support/src/lldp_collector.rs`:
- Around line 231-240: Update get_lldp_port_info to invoke lldpcli directly with
separate arguments for JSON output, neighbor display, and port, removing the
constructed bash -c command. Apply the same direct-argument approach to
get_local_chassis_id, preserving both commands’ existing behavior and error
handling.

---

Nitpick comments:
In `@crates/host-support/src/hardware_enumeration.rs`:
- Line 157: Change the visibility of convert_property_to_string from pub to
pub(crate), keeping its existing signature and behavior unchanged. Ensure the
lldp_collector crate-local reference continues to resolve through
crate::hardware_enumeration.

In `@crates/host-support/src/lldp_collector.rs`:
- Line 177: Update all five LLDP logging sites in lldp_collector.rs (lines 158,
177, 224, 237, and 255) to emit errors as structured tracing fields using error
= %e and concise static messages; preserve ifname and port fields where present,
and drop or demote raw JSON payloads to debug fields at the deserialization
sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 95d41ba3-c744-4d45-a312-0454ce9d3d75

📥 Commits

Reviewing files that changed from the base of the PR and between 81902e6 and 6a6185c.

📒 Files selected for processing (16)
  • crates/agent/src/command_line.rs
  • crates/agent/src/lib.rs
  • crates/agent/src/main.rs
  • crates/host-support/src/hardware_enumeration.rs
  • crates/host-support/src/hardware_enumeration/dpu.rs
  • crates/host-support/src/lib.rs
  • crates/host-support/src/lldp_collector.rs
  • crates/host-support/test/lldp_query.json
  • crates/rpc/proto/machine_discovery.proto
  • crates/rpc/src/model/hardware_info.rs
  • crates/scout/src/cfg/command_line.rs
  • crates/scout/src/main.rs
  • pxe/mkosi.profiles/scout-oss-aarch64/mkosi.conf
  • pxe/mkosi.profiles/scout-oss-aarch64/mkosi.postinst.chroot
  • pxe/mkosi.profiles/scout-oss-x86_64/mkosi.conf
  • pxe/mkosi.profiles/scout-oss-x86_64/mkosi.postinst.chroot
💤 Files with no reviewable changes (1)
  • crates/host-support/test/lldp_query.json

Comment thread crates/host-support/src/lldp_collector.rs
kurotych added 3 commits July 13, 2026 14:14
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
…DP collector and proto

Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
@ajf

ajf commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

/ok to test 1640a6f

@github-actions

Copy link
Copy Markdown

🔐 TruffleHog Secret Scan

No secrets or credentials found!

Your code has been scanned for 700+ types of secrets and credentials. All clear! 🎉

🔗 View scan details

🕐 Last updated: 2026-07-14 16:30:13 UTC | Commit: 1640a6f

@kurotych

kurotych commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Failed NICo REST CI is fixed

❯ cargo make --no-workspace check-repo-clean
[cargo-make] INFO - cargo make 0.37.24
[cargo-make] INFO -
[cargo-make] INFO - Build File: Makefile.toml
[cargo-make] INFO - Task: check-repo-clean
[cargo-make] INFO - Profile: development
[cargo-make] INFO - Running Task: check-repo-clean
OK: cargo make check-repo-clean left the repository clean.
[cargo-make] INFO - Build Done in 0.95 seconds.

@ajf

@github-actions

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 259 13 30 78 7 131
machine-validation-runner 807 40 234 288 36 209
machine_validation 807 40 234 288 36 209
machine_validation-aarch64 807 40 234 288 36 209
TOTAL 2686 133 732 948 115 758

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants